/*
 * MinIO Cloud Storage, (C) 2019 MinIO, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ldap

import (
	"crypto/sha1"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"fmt"
	"math/rand"
	"net"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/minio/minio/cmd/config"
	"github.com/minio/minio/pkg/env"
	"github.com/minio/minio/pkg/madmin"
	ldap "gopkg.in/ldap.v3"
)

const (
	defaultLDAPExpiry = time.Hour * 1
)

// Config contains AD/LDAP server connectivity information.
type Config struct {
	Enabled bool `json:"enabled"`

	// E.g. "ldap.minio.io:636"
	ServerAddr string `json:"serverAddr"`

	// STS credentials expiry duration
	STSExpiryDuration string `json:"stsExpiryDuration"`

	// Format string for usernames
	UsernameFormat        string   `json:"usernameFormat"`
	UsernameFormats       []string `json:"-"`
	UsernameSearchFilter  string   `json:"-"`
	UsernameSearchBaseDNS []string `json:"-"`

	GroupSearchBaseDN     string   `json:"groupSearchBaseDN"`
	GroupSearchBaseDNS    []string `json:"-"`
	GroupSearchFilter     string   `json:"groupSearchFilter"`
	GroupInfoSearchFilter string   `json:"groupInfoSearchFilter"`
	GroupNameAttribute    string   `json:"groupNameAttribute"`
	GroupNameFormat       string   `json:"groupNameFormat"`

	PolicySearchBaseDN  string   `json:"policySearchBaseDN"`
	PolicySearchBaseDNS []string `json:"-"`
	PolicySearchFilter  string   `json:"policySearchFilter"`
	PolicyNameFormat    string   `json:"policyNameFormat"`

	// LDAP Admin Account
	LDAPAdminUsername string `json:"ldapAdminUsername"`
	LDAPAdminPassword string `json:"ldapAdminPassword"`

	stsExpiryDuration time.Duration // contains converted value
	tlsSkipVerify     bool          // allows skipping TLS verification
	serverInsecure    bool          // allows plain text connection to LDAP Server
	serverStartTLS    bool          // allows plain text connection to LDAP Server
	rootCAs           *x509.CertPool
}

// LDAP keys and envs.
const (
	ServerAddr            = "server_addr"
	STSExpiry             = "sts_expiry"
	UsernameFormat        = "username_format"
	UsernameSearchFilter  = "username_search_filter"
	UsernameSearchBaseDN  = "username_search_base_dn"
	GroupSearchFilter     = "group_search_filter"
	GroupInfoSearchFilter = "group_info_search_filter"
	GroupNameAttribute    = "group_name_attribute"
	GroupSearchBaseDN     = "group_search_base_dn"
	GroupNameFormat       = "group_name_format"
	PolicySearchFilter    = "policy_search_filter"
	PolicySearchBaseDN    = "policy_search_base_dn"
	PolicyNameFormat      = "policy_name_format"
	TLSSkipVerify         = "tls_skip_verify"
	ServerInsecure        = "server_insecure"
	ServerStartTLS        = "server_starttls"
	LDAPAdminUsername     = "ldap_admin_username"
	LDAPAdminPassword     = "ldap_admin_password"

	EnvServerAddr            = "MINIO_IDENTITY_LDAP_SERVER_ADDR"
	EnvSTSExpiry             = "MINIO_IDENTITY_LDAP_STS_EXPIRY"
	EnvTLSSkipVerify         = "MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"
	EnvServerInsecure        = "MINIO_IDENTITY_LDAP_SERVER_INSECURE"
	EnvServerStartTLS        = "MINIO_IDENTITY_LDAP_SERVER_STARTTLS"
	EnvUsernameFormat        = "MINIO_IDENTITY_LDAP_USERNAME_FORMAT"
	EnvUsernameSearchFilter  = "MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER"
	EnvUsernameSearchBaseDN  = "MINIO_IDENTITY_LDAP_USERNAME_SEARCH_BASE_DN"
	EnvGroupSearchFilter     = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER"
	EnvGroupNameAttribute    = "MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE"
	EnvGroupSearchBaseDN     = "MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN"
	EnvGroupInfoSearchFilter = "MINIO_IDENTITY_LDAP_GROUP_INFO_SEARCH_FILTER"
	EnvGroupNameFormat       = "MINIO_IDENTITY_LDAP_GROUP_NAME_FORMAT"
	EnvLDAPAdminUsername     = "MINIO_IDENTITY_LDAP_ADMIN_USERNAME"
	EnvLDAPAdminPassword     = "MINIO_IDENTITY_LDAP_ADMIN_PASSWORD"
	EnvPolicySearchFilter    = "MINIO_IDENTITY_LDAP_POLICY_SEARCH_FILTER"
	EnvPolicySearchBaseDN    = "MINIO_IDENTITY_LDAP_POLICY_SEARCH_BASE_DN"
	EnvPolicyNameFormat      = "MINIO_IDENTITY_LDAP_POLICY_NAME_FORMAT"
)

// DefaultKVS - default config for LDAP config
var (
	DefaultKVS = config.KVS{
		config.KV{
			Key:   ServerAddr,
			Value: "",
		},
		config.KV{
			Key:   UsernameFormat,
			Value: "",
		},
		config.KV{
			Key:   UsernameSearchFilter,
			Value: "",
		},
		config.KV{
			Key:   UsernameSearchBaseDN,
			Value: "",
		},
		config.KV{
			Key:   GroupSearchFilter,
			Value: "",
		},
		config.KV{
			Key:   GroupInfoSearchFilter,
			Value: "",
		},
		config.KV{
			Key:   GroupNameAttribute,
			Value: "",
		},
		config.KV{
			Key:   GroupSearchBaseDN,
			Value: "",
		},
		config.KV{
			Key:   GroupNameFormat,
			Value: "",
		},
		config.KV{
			Key:   PolicySearchFilter,
			Value: "",
		},
		config.KV{
			Key:   PolicySearchBaseDN,
			Value: "",
		},
		config.KV{
			Key:   PolicyNameFormat,
			Value: "",
		},
		config.KV{
			Key:   STSExpiry,
			Value: "1h",
		},
		config.KV{
			Key:   TLSSkipVerify,
			Value: config.EnableOff,
		},
		config.KV{
			Key:   ServerInsecure,
			Value: config.EnableOff,
		},
		config.KV{
			Key:   ServerStartTLS,
			Value: config.EnableOff,
		},
		config.KV{
			Key:   LDAPAdminUsername,
			Value: "cn=admin,dc=wf,dc=cn",
		},
		config.KV{
			Key:   LDAPAdminPassword,
			Value: "admin",
		},
	}
)

const (
	dnDelimiter = ";"
)

func getGroups(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
	var groups []string
	sres, err := conn.Search(sreq)
	if err != nil {
		return nil, err
	}
	for _, entry := range sres.Entries {
		// We only queried one attribute,
		// so we only look up the first one.
		groups = append(groups, entry.Attributes[0].Values...)
	}
	return groups, nil
}

func getGroupDesc(conn *ldap.Conn, sreq *ldap.SearchRequest) (madmin.GroupDesc, error) {
	var group madmin.GroupDesc
	sres, err := conn.Search(sreq)
	if err != nil {
		return group, err
	}
	for _, entry := range sres.Entries {
		if len(entry.Attributes) >= 1 {
			group = madmin.GroupDesc{
				Name:    entry.GetAttributeValues("cn")[0],
				Members: entry.GetAttributeValues("memberUid"),
				Status:  "enabled",
				// Policy:  entry.Attributes[2].Values[0],
				Policy: entry.GetAttributeValue("description"),
			}
			break
		}
	}
	return group, nil
}

func getUsers(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
	var users []string
	sres, err := conn.Search(sreq)
	if err != nil {
		return nil, err
	}
	for _, entry := range sres.Entries {
		// We only queried one attribute,
		// so we only look up the first one.
		users = append(users, entry.Attributes[0].Values...)
	}
	return users, nil
}

func getUserInfo(conn *ldap.Conn, sreq *ldap.SearchRequest) (madmin.UserInfo, error) {
	var user madmin.UserInfo
	sres, err := conn.Search(sreq)
	if err != nil {
		return user, err
	}
	for _, entry := range sres.Entries {
		// We only queried one attribute,
		// so we only look up the first one.
		if len(entry.Attributes) >= 3 {
			user.PolicyName = entry.GetAttributeValue("stsusername")
			if entry.GetAttributeValue("userstatus") == string(madmin.AccountEnabled) {
				user.Status = madmin.AccountEnabled
			} else {
				user.Status = madmin.AccountDisabled
			}
			user.SecretKey = entry.GetAttributeValue("stsuserpassword")
			user.PolicyName = entry.GetAttributeValue("userpolicy")
		}
	}
	return user, nil
}

func getUserPolicy(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
	var policies []string
	sres, err := conn.Search(sreq)
	if err != nil {
		return nil, err
	}
	for _, entry := range sres.Entries {
		// We only queried one attribute,
		// so we only look up the first one.
		if len(entry.Attributes) > 0 {
			policies = append(policies, entry.GetAttributeValues("userpolicy")[0])
		}
	}
	return policies, nil
}

func getPolicyList(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
	var policies []string
	sres, err := conn.Search(sreq)
	if err != nil {
		return nil, err
	}
	for _, entry := range sres.Entries {
		// We only queried one attribute,
		// so we only look up the first one.
		if len(entry.Attributes) > 0 {
			policies = append(policies, entry.GetAttributeValue("policyname"))
		}
	}
	return policies, nil
}

func getPolicy(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
	var policies []string
	sres, err := conn.Search(sreq)
	if err != nil {
		return nil, err
	}
	for _, entry := range sres.Entries {
		// We only queried one attribute,
		// so we only look up the first one.
		if len(entry.Attributes) > 0 {
			policies = append(policies, entry.GetAttributeValue("policy"))
		}
	}
	return policies, nil
}

//func modifyAttributes(conn *ldap.Conn, mreq *ldap.ModifyRequest) (err error){
//
//}

func (l *Config) bind(conn *ldap.Conn, username, password string) ([]string, error) {
	var bindDNS = make([]string, len(l.UsernameFormats))
	for i, usernameFormat := range l.UsernameFormats {
		bindDN := fmt.Sprintf(usernameFormat, username)
		// Bind with user credentials to validate the password
		if err := conn.Bind(bindDN, password); err != nil {
			return nil, err
		}
		bindDNS[i] = bindDN
	}
	return bindDNS, nil
}

var standardAttributes = []string{
	"givenName",
	"sn",
	"cn",
	"memberOf",
	"email",
}

var userPolicyAttributes = []string{
	"userpolicy",
}

var userAttributes = []string{
	"uid",
	"stsusername",
	"stsuserpassword",
	"userstatus",
	"stsmemberof",
	"userpolicy",
	//"userPassword",
	//"description",
}

var groupAttributes = []string{
	"cn",
	"memberUid",
	"description",
}

var iamPolicyAttributes = []string{
	"policyname",
	"policy",
}

// Bind - binds to ldap, searches LDAP and returns list of groups.
func (l *Config) Bind(username, password string) ([]string, []string, []string, error) {
	conn, err := l.Connect()
	if err != nil {
		return nil, nil, nil, err
	}
	defer conn.Close()

	bindDNS, err := l.bind(conn, username, password)
	if err != nil {
		return nil, nil, nil, err
	}

	var groups []string
	// if l.UsernameSearchFilter != "" {
	// 	for _, userSearchBase := range l.UsernameSearchBaseDNS {
	// 		filter := strings.Replace(l.UsernameSearchFilter, "%s",
	// 			ldap.EscapeFilter(username), -1)

	// 		searchRequest := ldap.NewSearchRequest(
	// 			userSearchBase,
	// 			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
	// 			filter,
	// 			standardAttributes,
	// 			nil,
	// 		)

	// 		groups, err = getGroups(conn, searchRequest)
	// 		if err != nil {
	// 			return nil, nil, err
	// 		}
	// 	}
	// }

	// fmt.Println("Get Policy 0")
	// fmt.Println(groups)

	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return nil, nil, nil, err
	}

	if l.GroupSearchFilter != "" {
		for _, groupSearchBase := range l.GroupSearchBaseDNS {
			var filters []string
			if l.GroupNameAttribute == "" {
				filters = []string{strings.Replace(l.GroupSearchFilter, "%s",
					ldap.EscapeFilter(username), -1)}
			} else {
				// With group name attribute specified, make sure to
				// include search queries for CN distinguished name
				for _, bindDN := range bindDNS {
					dnString := ldap.EscapeFilter(bindDN)
					// Add posixGroup support
					if strings.Contains(l.GroupSearchFilter, "posixGroup") {
						memberUID := strings.Split(strings.Split(dnString, ",")[0], "=")[1]
						filters = append(filters, strings.Replace(l.GroupSearchFilter, "%s",
							memberUID, -1))
					} else {
						filters = append(filters, strings.Replace(l.GroupSearchFilter, "%s",
							ldap.EscapeFilter(bindDN), -1))
					}
				}
			}
			for _, filter := range filters {
				searchRequest := ldap.NewSearchRequest(
					groupSearchBase,
					ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
					filter,
					groupAttributes,
					nil,
				)

				var newGroups []string
				newGroups, err = getGroups(conn, searchRequest)
				if err != nil {
					return nil, nil, nil, err
				}

				groups = append(groups, newGroups...)
			}
		}
	}

	var policies []string
	for _, userSearchBase := range l.UsernameSearchBaseDNS {
		filter := strings.Replace(l.UsernameSearchFilter, "%s",
			ldap.EscapeFilter(username), -1)

		userPolicySearchRequest := ldap.NewSearchRequest(
			userSearchBase,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			filter,
			userPolicyAttributes,
			nil,
		)
		policies, err = getUserPolicy(conn, userPolicySearchRequest)
		if err != nil {
			return groups, nil, nil, err
		}
	}

	var policiesStr []string
	if len(policies) <= 0 {
		return groups, nil, nil, err
	}
	policyName := policies[0]
	policyFilter := strings.Replace(l.PolicySearchFilter, "%s",
		policyName, -1)
	for _, policySearchBaseDN := range l.PolicySearchBaseDNS {

		policySearchRequest := ldap.NewSearchRequest(
			policySearchBaseDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			policyFilter,
			iamPolicyAttributes,
			nil,
		)
		policiesStr, err = getPolicy(conn, policySearchRequest)
		if err != nil {
			return groups, policies, nil, err
		}
	}

	return groups, policies, policiesStr, nil
}

// GetUserList - binds to ldap, searches LDAP and returns list of users.
func (l *Config) GetUserList() ([]string, error) {
	conn, err := l.Connect()
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	var users []string

	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return users, err
	}

	for _, userSearchBase := range l.UsernameSearchBaseDNS {
		filter := strings.Replace(l.UsernameSearchFilter, "%s",
			"*", -1)

		usersSearchRequest := ldap.NewSearchRequest(
			userSearchBase,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			filter,
			userAttributes,
			nil,
		)
		users, err = getUsers(conn, usersSearchRequest)
		if err != nil {
			return users, err
		}
	}

	return users, nil
}

// GetUserList - binds to ldap, searches LDAP and returns list of users.
func (l *Config) GetUserInfo(userName string) (madmin.UserInfo, error) {
	var user madmin.UserInfo
	conn, err := l.Connect()
	if err != nil {
		return user, err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return user, err
	}
	defer conn.Close()

	for _, userSearchBase := range l.UsernameSearchBaseDNS {
		filter := strings.Replace(l.UsernameSearchFilter, "%s",
			userName, -1)

		usersSearchRequest := ldap.NewSearchRequest(
			userSearchBase,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			filter,
			userAttributes,
			nil,
		)
		user, err = getUserInfo(conn, usersSearchRequest)
		if err != nil {
			return user, err
		}
	}

	return user, nil
}

// GetGroupList - binds to ldap, searches LDAP and returns list of groups.
func (l *Config) GetGroupList() ([]string, error) {
	conn, err := l.Connect()
	if err != nil {
		return nil, err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	var groups []string

	for _, groupSearchBase := range l.GroupSearchBaseDNS {
		filter := strings.Replace(l.GroupInfoSearchFilter, "%s",
			"*", -1)

		groupsSearchRequest := ldap.NewSearchRequest(
			groupSearchBase,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			filter,
			groupAttributes,
			nil,
		)
		groups, err = getGroups(conn, groupsSearchRequest)
		if err != nil {
			return groups, err
		}
	}

	return groups, nil
}

// GetGroupInfo - binds to ldap, searches LDAP and returns user's groups.
func (l *Config) GetGroupInfo(groupName string) (madmin.GroupDesc, error) {
	var group madmin.GroupDesc
	conn, err := l.Connect()
	if err != nil {
		return group, err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return group, err
	}
	defer conn.Close()

	//var groups []string

	for _, groupSearchBase := range l.GroupSearchBaseDNS {
		filter := strings.Replace(l.GroupInfoSearchFilter, "%s",
			groupName, -1)

		groupsSearchRequest := ldap.NewSearchRequest(
			groupSearchBase,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			filter,
			groupAttributes,
			nil,
		)
		group, err = getGroupDesc(conn, groupsSearchRequest)
		if err != nil {
			return group, err
		}
	}

	return group, nil
}

// GetUserGroupInfo
func (l *Config) GetUserGroupInfo(userName string) ([]string, error) {
	conn, err := l.Connect()
	if err != nil {
		return nil, err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	var groups []string

	for _, groupSearchBase := range l.GroupSearchBaseDNS {
		filter := strings.Replace(l.GroupSearchFilter, "%s",
			userName, -1)

		groupsSearchRequest := ldap.NewSearchRequest(
			groupSearchBase,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			filter,
			groupAttributes,
			nil,
		)
		groups, err = getGroups(conn, groupsSearchRequest)
		if err != nil {
			return groups, err
		}
	}

	return groups, nil
}

// SetUserStatus
func (l *Config) SetUserStatus(userName string, userStatus string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	for _, userSearchDN := range l.UsernameFormats {
		userDN := strings.Replace(userSearchDN, "%s", userName, -1)

		modifyStatusRequest := ldap.NewModifyRequest(userDN, nil)

		modifyStatusRequest.Replace("userstatus", []string{userStatus})

		err = conn.Modify(modifyStatusRequest)

		if err != nil {
			return err
		}
	}
	return nil
}

// SetUserStatus
func (l *Config) RemoveUsersFromGroup(groupName string, users []string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	groupDN := strings.Replace(l.GroupNameFormat, "%s", groupName, -1)

	userGroupList, err := l.GetGroupInfo(groupName)
	if err != nil {
		return err
	}
	userList := userGroupList.Members
	var newUserList []string
	for _, user := range userList {
		if l.In(user, users) {
			continue
		}
		newUserList = append(newUserList, user)
	}
	removeUserFromGroupRequest := ldap.NewModifyRequest(groupDN, nil)
	removeUserFromGroupRequest.Replace("memberUid", newUserList)
	//return nil
	err = conn.Modify(removeUserFromGroupRequest)
	return err
}

// SetUserStatus
func (l *Config) AddUsersToGroup(groupName string, users []string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	groupDN := strings.Replace(l.GroupNameFormat, "%s", groupName, -1)

	userGroupList, err := l.GetGroupInfo(groupName)
	if err != nil {
		return err
	}
	userList := userGroupList.Members
	for _, user := range users {
		userList = append(userList, user)
	}

	addUserToGroupRequest := ldap.NewModifyRequest(groupDN, nil)
	addUserToGroupRequest.Replace("memberUid", userList)

	//return nil
	err = conn.Modify(addUserToGroupRequest)
	return err
}

// SetGroup
func (l *Config) SetGroup(groupName string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	groupDN := strings.Replace(l.GroupNameFormat, "%s", groupName, -1)

	addGroupRequest := ldap.NewAddRequest(groupDN, nil)
	addGroupRequest.Attribute("objectClass", []string{"posixGroup", "top"})
	addGroupRequest.Attribute("cn", []string{groupName})
	rand.Seed(time.Now().UnixNano())
	gidNumber := rand.Intn(5000) + 1000
	addGroupRequest.Attribute("gidNumber", []string{strconv.Itoa(gidNumber)})

	err = conn.Add(addGroupRequest)
	if err != nil {
		return err
	}

	return nil
}

// SetUserStatus
func (l *Config) SetUser(userName string, userInfo madmin.UserInfo) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	//groupDN := strings.Replace(l.GroupNameFormat, "%s", groupName, -1)

	for _, usernameBaseDN := range l.UsernameFormats {
		userDN := strings.Replace(usernameBaseDN, "%s", userName, -1)
		addUserRequest := ldap.NewAddRequest(userDN, nil)
		addUserRequest.Attribute("objectClass", []string{"posixAccount", "top", "inetOrgPerson", "stsUser"})
		addUserRequest.Attribute("sn", []string{userName})
		rand.Seed(time.Now().UnixNano())
		gidNumber := rand.Intn(50000) + 10000
		rand.Seed(time.Now().UnixNano())
		uidNumber := rand.Intn(50000) + 10000
		addUserRequest.Attribute("gidNumber", []string{strconv.Itoa(gidNumber)})
		addUserRequest.Attribute("uid", []string{userName})
		addUserRequest.Attribute("homeDirectory", []string{userName})
		addUserRequest.Attribute("cn", []string{userName})
		addUserRequest.Attribute("uidNumber", []string{strconv.Itoa(uidNumber)})
		addUserRequest.Attribute("stsusername", []string{userName})
		addUserRequest.Attribute("stsuserpassword", []string{userInfo.SecretKey})
		addUserRequest.Attribute("userstatus", []string{string(userInfo.Status)})

		passwordSHA1 := sha1.Sum([]byte(userInfo.SecretKey))
		userPassword := base64.StdEncoding.EncodeToString(passwordSHA1[:])
		userPassword = "{SHA}" + userPassword
		addUserRequest.Attribute("userPassword", []string{userPassword})

		err := conn.Add(addUserRequest)
		if err != nil {
			return err
		}
	}
	return nil
}

// DeleteUser
func (l *Config) DeleteUser(userName string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	//groupDN := strings.Replace(l.GroupNameFormat, "%s", groupName, -1)

	for _, usernameBaseDN := range l.UsernameFormats {
		userDN := strings.Replace(usernameBaseDN, "%s", userName, -1)
		deleteUserRequest := ldap.NewDelRequest(userDN, nil)

		err := conn.Del(deleteUserRequest)
		if err != nil {
			return err
		}
	}
	return nil
}

// DeleteGroup
func (l *Config) DeleteGroup(groupName string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	groupDN := strings.Replace(l.GroupNameFormat, "%s", groupName, -1)

	deleteGroupRequest := ldap.NewDelRequest(groupDN, nil)

	err = conn.Del(deleteGroupRequest)
	if err != nil {
		return err
	}

	return nil
}

// GetPolicyList
func (l *Config) GetPolicyList() ([]string, error) {
	conn, err := l.Connect()
	if err != nil {
		return nil, err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	var policies []string

	for _, policySearchBase := range l.PolicySearchBaseDNS {
		filter := strings.Replace(l.PolicySearchFilter, "%s",
			"*", -1)

		policySearchRequest := ldap.NewSearchRequest(
			policySearchBase,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			filter,
			iamPolicyAttributes,
			nil,
		)
		policies, err = getPolicyList(conn, policySearchRequest)
		if err != nil {
			return policies, err
		}
	}
	return policies, nil
}

// SetGroup
func (l *Config) SetPolicy(policyName string, policy string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	policyDN := strings.Replace(l.PolicyNameFormat, "%s", policyName, -1)

	addPolicyRequest := ldap.NewAddRequest(policyDN, nil)
	addPolicyRequest.Attribute("objectClass", []string{"account", "top", "stsPolicy"})
	addPolicyRequest.Attribute("policyname", []string{policyName})
	addPolicyRequest.Attribute("uid", []string{policyName})
	addPolicyRequest.Attribute("policy", []string{policy})

	err = conn.Add(addPolicyRequest)
	if err != nil {
		return err
	}

	return nil
}

// ModifyPolicy
func (l *Config) ModifyPolicy(policyName string, policy string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	policyDN := strings.Replace(l.PolicyNameFormat, "%s", policyName, -1)

	modifyPolicyRequest := ldap.NewModifyRequest(policyDN, nil)
	modifyPolicyRequest.Replace("policy", []string{policy})

	err = conn.Modify(modifyPolicyRequest)
	if err != nil {
		return err
	}
	return nil
}

// DeletePolicy
func (l *Config) DeletePolicy(policyName string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	policyDN := strings.Replace(l.PolicyNameFormat, "%s", policyName, -1)

	deletePolicyRequest := ldap.NewDelRequest(policyDN, nil)

	err = conn.Del(deletePolicyRequest)
	if err != nil {
		return err
	}

	return nil
}

// SetUserPolicy
func (l *Config) SetUserPolicy(userName string, policyName string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	for _, userSearchDN := range l.UsernameFormats {
		userDN := strings.Replace(userSearchDN, "%s", userName, -1)

		modifyStatusRequest := ldap.NewModifyRequest(userDN, nil)

		modifyStatusRequest.Replace("userpolicy", []string{policyName})

		err = conn.Modify(modifyStatusRequest)

		if err != nil {
			return err
		}
	}
	return nil
}

// SetUserPolicy
func (l *Config) SetGroupPolicy(groupName string, policyName string) (err error) {
	conn, err := l.Connect()
	if err != nil {
		return err
	}
	err = conn.Bind(l.LDAPAdminUsername, l.LDAPAdminPassword)
	if err != nil {
		return err
	}
	defer conn.Close()

	groupDN := strings.Replace(l.GroupNameFormat, "%s", groupName, -1)

	modifyGroupPolicyRequest := ldap.NewModifyRequest(groupDN, nil)

	modifyGroupPolicyRequest.Replace("description", []string{policyName})

	err = conn.Modify(modifyGroupPolicyRequest)

	if err != nil {
		return err
	}

	return nil
}

//func (l *Config) SyncPolicy()

// Connect connect to ldap server.
func (l *Config) Connect() (ldapConn *ldap.Conn, err error) {
	if l == nil {
		return nil, errors.New("LDAP is not configured")
	}

	if _, _, err = net.SplitHostPort(l.ServerAddr); err != nil {
		// User default LDAP port if none specified "636"
		l.ServerAddr = net.JoinHostPort(l.ServerAddr, "636")
	}

	if l.serverInsecure {
		return ldap.Dial("tcp", l.ServerAddr)
	}

	if l.serverStartTLS {
		conn, err := ldap.Dial("tcp", l.ServerAddr)
		if err != nil {
			return nil, err
		}
		err = conn.StartTLS(&tls.Config{
			InsecureSkipVerify: l.tlsSkipVerify,
			RootCAs:            l.rootCAs,
		})
		return conn, err
	}

	return ldap.DialTLS("tcp", l.ServerAddr, &tls.Config{
		InsecureSkipVerify: l.tlsSkipVerify,
		RootCAs:            l.rootCAs,
	})
}

// GetExpiryDuration - return parsed expiry duration.
func (l Config) GetExpiryDuration() time.Duration {
	return l.stsExpiryDuration
}

// Enabled returns if jwks is enabled.
func Enabled(kvs config.KVS) bool {
	return kvs.Get(ServerAddr) != ""
}

// Lookup - initializes LDAP config, overrides config, if any ENV values are set.
func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
	l = Config{}
	if err = config.CheckValidKeys(config.IdentityLDAPSubSys, kvs, DefaultKVS); err != nil {
		return l, err
	}
	ldapServer := env.Get(EnvServerAddr, kvs.Get(ServerAddr))
	if ldapServer == "" {
		return l, nil
	}
	l.Enabled = true
	l.ServerAddr = ldapServer
	l.stsExpiryDuration = defaultLDAPExpiry
	if v := env.Get(EnvSTSExpiry, kvs.Get(STSExpiry)); v != "" {
		expDur, err := time.ParseDuration(v)
		if err != nil {
			return l, errors.New("LDAP expiry time err:" + err.Error())
		}
		if expDur <= 0 {
			return l, errors.New("LDAP expiry time has to be positive")
		}
		l.STSExpiryDuration = v
		l.stsExpiryDuration = expDur
	}
	if v := env.Get(EnvServerInsecure, kvs.Get(ServerInsecure)); v != "" {
		l.serverInsecure, err = config.ParseBool(v)
		if err != nil {
			return l, err
		}
	}
	if v := env.Get(EnvServerStartTLS, kvs.Get(ServerStartTLS)); v != "" {
		l.serverStartTLS, err = config.ParseBool(v)
		if err != nil {
			return l, err
		}
	}
	if v := env.Get(EnvTLSSkipVerify, kvs.Get(TLSSkipVerify)); v != "" {
		l.tlsSkipVerify, err = config.ParseBool(v)
		if err != nil {
			return l, err
		}
	}
	if v := env.Get(EnvUsernameFormat, kvs.Get(UsernameFormat)); v != "" {
		if !strings.Contains(v, "%s") {
			return l, errors.New("LDAP username format doesn't have '%s' substitution")
		}
		l.UsernameFormats = strings.Split(v, dnDelimiter)
	} else {
		return l, fmt.Errorf("'%s' cannot be empty and must have a value", UsernameFormat)
	}

	if v := env.Get(EnvUsernameSearchFilter, kvs.Get(UsernameSearchFilter)); v != "" {
		if !strings.Contains(v, "%s") {
			return l, errors.New("LDAP username search filter doesn't have '%s' substitution")
		}
		l.UsernameSearchFilter = v
	}

	if v := env.Get(EnvUsernameSearchBaseDN, kvs.Get(UsernameSearchBaseDN)); v != "" {
		l.UsernameSearchBaseDNS = strings.Split(v, dnDelimiter)
	}

	grpSearchFilter := env.Get(EnvGroupSearchFilter, kvs.Get(GroupSearchFilter))
	grpInfoSearchFilter := env.Get(EnvGroupInfoSearchFilter, kvs.Get(GroupInfoSearchFilter))
	grpSearchNameAttr := env.Get(EnvGroupNameAttribute, kvs.Get(GroupNameAttribute))
	grpSearchBaseDN := env.Get(EnvGroupSearchBaseDN, kvs.Get(GroupSearchBaseDN))
	grpNameFormat := env.Get(EnvGroupNameFormat, kvs.Get(GroupNameFormat))

	ldapAdmUsername := env.Get(EnvLDAPAdminUsername, kvs.Get(LDAPAdminUsername))
	ldapAdmPassword := env.Get(EnvLDAPAdminPassword, kvs.Get(LDAPAdminPassword))

	pcySearchFilter := env.Get(EnvPolicySearchFilter, kvs.Get(PolicySearchFilter))
	pcySearchBaseDN := env.Get(EnvPolicySearchBaseDN, kvs.Get(PolicySearchBaseDN))
	pcyNameFormat := env.Get(EnvPolicyNameFormat, kvs.Get(PolicyNameFormat))

	// Either all group params must be set or none must be set.
	var allSet bool
	if grpSearchFilter != "" {
		if grpSearchNameAttr == "" || grpSearchBaseDN == "" || grpInfoSearchFilter == "" || grpNameFormat == "" {
			return l, errors.New("All group related parameters must be set")
		}
		allSet = true
	}

	var adminSet bool
	if ldapAdmUsername == "" || ldapAdmPassword == "" {
		return l, errors.New("LDAP admin username and password must be set")
	}
	adminSet = true

	var policySet bool
	if pcySearchFilter != "" {
		if pcySearchBaseDN == "" || pcyNameFormat == "" {
			return l, errors.New("All policy related parameters must be set")
		}
		policySet = true
	}

	if adminSet {
		l.LDAPAdminUsername = ldapAdmUsername
		l.LDAPAdminPassword = ldapAdmPassword
	}

	if allSet {
		l.GroupSearchFilter = grpSearchFilter
		l.GroupInfoSearchFilter = grpInfoSearchFilter
		l.GroupNameAttribute = grpSearchNameAttr
		l.GroupSearchBaseDNS = strings.Split(grpSearchBaseDN, dnDelimiter)
		l.GroupNameFormat = grpNameFormat
	}

	if policySet {
		l.PolicySearchFilter = pcySearchFilter
		l.PolicySearchBaseDNS = strings.Split(pcySearchBaseDN, dnDelimiter)
		l.PolicyNameFormat = pcyNameFormat
	}

	l.rootCAs = rootCAs
	return l, nil
}

func (l *Config) In(target string, str_array []string) bool {
	sort.Strings(str_array)
	index := sort.SearchStrings(str_array, target)
	//index的取值：[0,len(str_array)]
	if index < len(str_array) && str_array[index] == target {
		//需要注意此处的判断，先判断 &&左侧的条件，如果不满足则结束此处判断，不会再进行右侧的判断
		return true
	}
	return false
}
